/*
 * Copyright 2007-2009 Perttu "celeron55" Ahola <celeron55@gmail.com>
 *
 * This file is part of jukumagic.
 *
 * jukumagic is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * jukumagic is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with jukumagic.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "jukuapp.h"
#include "jukuframe.h"
#include "boardwindow.h"
#include "common.h"
#include "tinyxml/tinyxml.h"

#include "xml.h"
#include "project.h"
#include "board.h"
#include "jumperwire.h"
#include "component.h"
#include "footprint.h"
#include "pin.h"
#include "rect.h"
#include "arc.h"
#include "line.h"
#include "footprintlibrarynode.h"
#include "cut.h"
#include "narrowcut.h"
#include "net.h"

IMPLEMENT_APP(JukuApp)

#if 0
FootprintLibraryNode* MakeTwoWireNode(FootprintType type, int len_holes)
{
	int len = len_holes*100;
	FootprintLibraryNode *node = new FootprintLibraryNode();
	Footprint *f = MakeTwoWireFootprint(type, len);
	if(!f) return NULL;
	node->m_name = f->m_name;
	node->Add((JukuObject*)f, NULL);
	return node;
}

struct GenerateListNode
{
	FootprintType type;
	int minlen;
	int maxlen;
};

GenerateListNode generatelist[] =
{
	{FOOTPRINT_ACY, 1, 10},
	{FOOTPRINT_ALF, 1, 10},
	{FOOTPRINT_ACC, 1, 10},
	{FOOTPRINT_RCY100, 1, 3},
	/*{FOOTPRINT_RCY200, 1, 2},*/
	/*{FOOTPRINT_RCY300, 3, 3},
	{FOOTPRINT_RCY400, 4, 4},
	{FOOTPRINT_RCY500, 5, 5},
	{FOOTPRINT_RCY600, 6, 6},*/
	{FOOTPRINT_UNKNOWN,-1,-1}
};
#endif

void JukuApp::ChildChanged(ObjectChangeType type, JukuObject *obj)
{
	if(obj == m_project && m_project != NULL){
		if(m_frame != NULL){
			m_frame->SetTitle(wxString(_T("jukuMagic - "))+m_project->GetFilepath());
		}
	}
	if(obj == &m_library){
		if(m_frame != NULL){
			m_frame->UpdateFootprintList();
		}
	}
}

JukuApp::JukuApp()
{
	//std::cout<<"JukuApp::JukuApp()"<<std::endl;

	m_project = NULL;
	m_frame = NULL;
	// This enables us to receive change events from the library.
	m_library.m_owner = this;
	m_initial_title = _T("jukuMagic");
	m_initial_status = _T("--");
	InitXMLFunctions();

	//m_library.AddDir(wxGetCwd());
	//m_library.AddDir(wxGetCwd()+wxT("/library"));
	//m_library.AddDir(wxGetHomeDir()+wxT("/jukumagic/library"));

#if 0
	//generate some footprints
	
	GenerateListNode *n = generatelist;
	while(n->minlen != -1){
		for(int i = n->minlen; i <= n->maxlen; i++){
			FootprintLibraryNode *node = MakeTwoWireNode(n->type, i);
			if(!node) continue;
			m_library.AddNode(node);
		}
		n++;
	}

	FootprintLibraryNode *node;
	node = MakeTwoWireNode(FOOTPRINT_RCY200, 2);
	if(node) m_library.AddNode(node);
	node = MakeTwoWireNode(FOOTPRINT_RCY200, 1);
	if(node) m_library.AddNode(node);
#endif

	//m_library.Sort();
}

JukuApp::~JukuApp()
{
	//std::cout<<"JukuApp::~JukuApp()"<<std::endl;
	if(m_project) delete m_project;
	m_project = NULL;
}

/*
	This happens after OnCmdLineParsed
*/
bool JukuApp::OnInit()
{
	//std::cerr<<"JukuApp::OnInit()"<<std::endl;
	
	/*
		For example something like --help on the command line will cause
		this to return false.
	*/
	bool e = wxApp::OnInit();
	if(e == false) return false;

	//std::cerr<<"JukuApp::OnInit(): wxApp::OnInit() == true -> continue"<<std::endl;

	m_frame = new JukuFrame();
	if(!m_frame) return false;
    m_frame->Show(TRUE);
	m_frame->SetTitle(m_initial_title);
	m_frame->SetStatusText(m_initial_status);
    SetTopWindow(m_frame);
	

	if(m_project){
		if(m_project->m_board){
			m_frame->SetBoard(m_project->m_board);
		}
		m_frame->SetNetlist(&(m_project->m_netlist));
	}
	
	m_pythonapi.ExecFile(_T("user_config.py"));

    return true;
}

// This is called at exit if OnInit() was called
int JukuApp::OnExit()
{
	//std::cout<<"JukuApp::OnExit()"<<std::endl;

	/*
		We have to delete these pointers if they're non-NULL:
		- m_project
		- ////m_frame IMPORTANT: No, wxFrames aren't supposed to be deleted
		                         anywhere by ourselves.
	*/

	// Let CloseProject know there is no m_frame any more (because there isn't)
	m_frame = NULL;
	
	CloseProject();
	
	int a = wxApp::OnExit();
	return a;
}

static const wxCmdLineEntryDesc g_cmdLineDesc [] =
{
     { wxCMD_LINE_SWITCH, wxT("h"), wxT("help"), wxT("Help"),
			wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
     { wxCMD_LINE_SWITCH, wxT("v"), wxT("verbose"), wxT("") },
     { wxCMD_LINE_PARAM,  wxT(""), wxT(""), wxT("filename"),
			wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
     /*{ wxCMD_LINE_OPTION, wxT("W"), wxT("width"), wxT("Width of board"),
			wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },
     { wxCMD_LINE_OPTION, wxT("H"), wxT("height"), wxT("Height of board"),
			wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL },*/
     { wxCMD_LINE_NONE }
};

void JukuApp::OnInitCmdLine(wxCmdLineParser& parser)
{
    parser.SetDesc (g_cmdLineDesc);
    // must refuse '/' as parameter starter or cannot use "/path" style paths
    parser.SetSwitchChars (wxT("-"));
}

/*
	This happens before OnInit
*/
bool JukuApp::OnCmdLineParsed(wxCmdLineParser& parser)
{
	//std::cerr<<"JukuApp::OnCmdLineParsed()"<<std::endl;

	m_settings.m_verbose_mode_on = parser.Found(wxT("v"));

	/*long int tempboardw, tempboardh;
	bool w_specified = parser.Found(wxT("W"), &tempboardw);
	if(!w_specified) boardw = 62;
	else boardw = tempboardw;
	bool h_specified = parser.Found(wxT("H"), &tempboardh);
	if(!h_specified) boardh = 39;
	else boardh = tempboardh;*/
	
	if(parser.GetParamCount()>0){
		wxString file_to_open = parser.GetParam(0);
		if(OpenProject(file_to_open)){
			//std::cout<<"Opened file"<<std::endl;
		}
		else{
			NewProject(file_to_open);
			std::cout<<"Opening file failed, new project made."<<std::endl;
			m_initial_status = wxT("Opening file failed, new project made.");
		}
	}
	else{
		NewProject(_T(""));
	}

	return true;
}

bool JukuApp::NewProject(wxString filepath)
{
	CloseProject();
	m_project = new Project();
	if(!m_project) return false;
	Board *board = new Board(m_settings.m_default_board_w, m_settings.m_default_board_h);
	if(!board){
		delete m_project;
		m_project = NULL;
		return false;
	}
	m_project->m_board = board;
	if(m_frame) m_frame->SetBoard(m_project->m_board);
	//if(m_frame) m_frame->SetNetlist(m_project->m_netlist);
	if(m_frame) m_frame->SetNetlist(&m_project->m_netlist);
	m_project->SetFilepath(filepath);
	//m_settings.m_current_file_path.Clear();
	//m_settings.m_current_file_path << m_project->GetFilepath() << wxT(".juku");

	return true;
}

bool JukuApp::OpenProject(wxString filepath)
{
	//std::cout<<"JukuApp::OpenProject(\""<<filepath.mb_str(wxConvUTF8)<<"\")"<<std::endl;
	TiXmlDocument doc(filepath.mb_str(wxConvUTF8));
	if(!doc.LoadFile()){
		std::cout<<"doc.LoadFile() failed"<<std::endl;
		return false;
	}
	if(doc.Error()){
		std::cout<<"doc.Error()"<<std::endl;
		return false;
	}

	TiXmlElement *root = doc.FirstChildElement("Jukumagic");
	if(root){
		int version;
		root->Attribute("version", &version);
		std::cout<<"root found, version="<<version<<std::endl;
		TiXmlElement *element = root->FirstChildElement("Project");
		if(element){
			std::cout<<"project found"<<std::endl;
			
			CloseProject();

			m_project = (Project*)Project::ReadFromXMLElement(element);
			m_project->m_owner = this;

			m_project->SetFilepath(filepath);

			wxString s;
			s << wxT("Loaded Project \"") << m_project->GetFilepath() << wxT("\"");
			std::cout<<s.mb_str(wxConvUTF8)<<std::endl;

			if(m_frame){
				m_frame->SetStatusText(s);
				if(m_project->m_board){
					m_frame->SetBoard(m_project->m_board);
				}
				/*if(m_project->m_netlist){
					m_frame->SetNetlist(m_project->m_netlist);
				}*/
				m_frame->SetNetlist(&m_project->m_netlist);
			}

			return true;
		}
		else{
			std::cout<<"no project element found"<<std::endl;
			//TiXmlElement *element = root->FirstChildElement("FootprintLibraryNode");
		}
	}
	else{
		std::cout<<"no root element found"<<std::endl;
	}

	return false;
}

bool JukuApp::SaveProject(wxString filepath)
{
	if(!m_project){
		std::cout<<"no project to save"<<std::endl;
		return false;
	}
	
	//std::cout<<"JukuApp::SaveProject(\""<<filepath.mb_str(wxConvUTF8)<<"\")"<<std::endl;
	TiXmlDocument doc(filepath.mb_str(wxConvUTF8));

	TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "", "");  
	doc.LinkEndChild(decl); 
	
	TiXmlElement *root = new TiXmlElement("Jukumagic");
	root->SetAttribute("version", "1");
	doc.LinkEndChild(root);
	
	m_project->WriteXML(root);
	
	if(!doc.SaveFile()){
		std::cout<<"doc.SaveProject() failed"<<std::endl;
		wxString s;
		s << wxT("Saved Project \"") << m_project->GetFilepath() << wxT("\"");
		std::cout<<s.mb_str(wxConvUTF8)<<std::endl;
		if(m_frame) m_frame->SetStatusText(s);
		return false;
	}
	return true;
}

bool JukuApp::CloseProject()
{
	if(!m_project){
		//std::cout<<"JukuApp::CloseProject(): no project to close"<<std::endl;
		return false;
	}
	
	if(m_frame){
		m_frame->SetBoard(NULL);
		m_frame->SetNetlist(NULL);
	}
	delete m_project;
	m_project = NULL;

	return true;
}

bool JukuApp::RegisterReadXMLFunction(const char *type_name, JukuObject*(*read_xml_function)(TiXmlElement *))
{
	if(xml_read_fn_map.find(type_name) != xml_read_fn_map.end()){
		//std::cout<<"xml read function already registered for type \""<<type_name<<"\"."<<std::endl;
		return false;
	}
	xml_read_fn_map.insert( std::pair< std::string, JukuObject*(*)(TiXmlElement*) > (type_name, read_xml_function));
	return true;
}

void JukuApp::InitXMLFunctions()
{
	//Only those which are stored in ObjectContainers

	//RegisterReadXMLFunction("Project", Project::ReadFromXMLElement);
	//RegisterReadXMLFunction("Board", Board::ReadFromXMLElement);
	RegisterReadXMLFunction("JumperWire", JumperWire::ReadFromXMLElement);
	RegisterReadXMLFunction("Component", Component::ReadFromXMLElement);
	RegisterReadXMLFunction("Footprint", Footprint::ReadFromXMLElement);
	RegisterReadXMLFunction("Pin", Pin::ReadFromXMLElement);
	RegisterReadXMLFunction("Rect", Rect::ReadFromXMLElement);
	RegisterReadXMLFunction("Arc", JArc::ReadFromXMLElement);
	RegisterReadXMLFunction("Line", Line::ReadFromXMLElement);
	//RegisterReadXMLFunction("FootprintLibraryNode", FootprintLibraryNode::ReadFromXMLElement);
	RegisterReadXMLFunction("Cut", Cut::ReadFromXMLElement);
	RegisterReadXMLFunction("NarrowCut", NarrowCut::ReadFromXMLElement);
	RegisterReadXMLFunction("Net", Net::ReadFromXMLElement);
}

JukuObject* JukuApp::ReadXMLElement(TiXmlElement *element)
{
	std::string name(element->Value());
	std::map< std::string, JukuObject*(*)(TiXmlElement *) >::iterator it = xml_read_fn_map.find(name);
	JukuObject *object = NULL;
	if(it != xml_read_fn_map.end()){
		object = (*(it->second))(element);
	}
	else{
		object = Xml::ReadFromXMLElement(element);
	}
	return object;
}

void JukuApp::AnytimeExit()
{
	std::cerr<<"JukuApp::AnytimeExit()"<<std::endl;
	if(m_frame){
		wxGetApp().m_frame->Close(false);
	}
	else{
		// This happens when OnInit wasn't yet called
		exit(0);
	}
}

